/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.struts2;

import cn.webwheel.template.ComponentRenderer;
import cn.webwheel.template.ComponentRendererBuilder;
import cn.webwheel.template.plain.PlainTemplateParser;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 使用WebWheel页面模板的struts2结果
 */
public class WebWheelResult extends StrutsResultSupport {

    private static class CID {
        String location;
        Class actionClass;

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            CID cid = (CID) o;

            if (!actionClass.equals(cid.actionClass)) return false;
            if (!location.equals(cid.location)) return false;

            return true;
        }

        public int hashCode() {
            int result;
            result = location.hashCode();
            result = 31 * result + actionClass.hashCode();
            return result;
        }
    }

    private static class FileSig {
        long lastModified;
        long lastTestTime;
        ComponentRenderer renderer;
    }

    private static Map<CID, FileSig> rendererMap = new ConcurrentHashMap<CID, FileSig>();

    private int cacheTTL = 5000;
    private String encoding = "utf-8";
    private String contentType = "text/html";

    /**
     * 模板页面的更新检查间隔时长，单位为毫秒，负值为不检查更新，默认5000
     * @param cacheTTL 缓存间隔
     */
    @Inject(value = "webwheel.cacheTTL", required = false)
    public void setTTL(String cacheTTL) {
        this.cacheTTL = Integer.parseInt(cacheTTL);
    }

    /**
     * 模板文件以及页面输出的编码格式，默认为utf-8
     * @param encoding 编码
     */
    @Inject(value = "webwheel.encoding", required = false)
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /**
     * 页面输出所使用的内容类型，默认为text/html
     * @param contentType 内容类型
     */
    @Inject(value = "webwheel.contentType", required = false)
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    @SuppressWarnings("unchecked")
    protected void doExecute(String location, ActionInvocation invocation) throws Exception {

        Object action = invocation.getAction();

        CID cid = new CID();
        cid.actionClass = action.getClass();
        cid.location = location;

        FileSig sig = rendererMap.get(cid);

        if (sig != null && cacheTTL >= 0) {
            long time = System.currentTimeMillis();
            if (time - sig.lastTestTime > cacheTTL) {
                File tempFile = new File(ServletActionContext.getServletContext().getRealPath(location));
                long lm = tempFile.lastModified();
                if (lm != sig.lastModified) {
                    sig = null;
                } else {
                    sig.lastTestTime = time;
                }
            }
        }

        if (sig == null) {
            File file = new File(ServletActionContext.getServletContext().getRealPath(location));

            Reader reader = new InputStreamReader(new FileInputStream(file), encoding);

            char[] buf = new char[8192];
            int rd;
            StringBuilder sb = new StringBuilder();

            try {
                while ((rd = reader.read(buf)) != -1) {
                    sb.append(buf, 0, rd);
                }
            } finally {
                reader.close();
            }

            sig = new FileSig();
            try {
                ComponentRendererBuilder builder = new ComponentRendererBuilder(action.getClass(), sb.toString());
                sig.renderer = (ComponentRenderer) builder.build().newInstance();
            } catch (PlainTemplateParser.TemplateParserException e) {
                throw new IOException("can not parse template: " + location, e);
            }
            sig.lastModified = file.lastModified();
            if (cacheTTL >= 0) {
                sig.lastTestTime = System.currentTimeMillis();
            }
            rendererMap.put(cid, sig);
        }

        HttpServletResponse response = ServletActionContext.getResponse();
        response.setContentType(contentType);
        response.setCharacterEncoding(encoding);
        sig.renderer.render(response.getWriter(), action, null);
    }
}
